package com.yiban.demo.Order.Service.ServiceImp;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Sequence;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yiban.demo.Order.DAO.OrderMapper;
import com.yiban.demo.Order.Entity.OrderPO;
import com.yiban.demo.Order.Entity.OrderStatus;
import com.yiban.demo.Order.Entity.OrderTransfer;
import com.yiban.demo.Order.Entity.OrderVO;
import com.yiban.demo.Order.OrderEvent.DeliveryOrderEvent;
import com.yiban.demo.Order.OrderEvent.EndOrderEvent;
import com.yiban.demo.Order.OrderEvent.NewOrderEvent;
import com.yiban.demo.Station.Entity.StationOrderMessage;
import com.yiban.demo.Exception.MessageServiceException;
import com.yiban.demo.Order.Service.OrderService;
import com.yiban.demo.Station.Service.StationService;
import com.yiban.demo.Util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PreDestroy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImp extends ServiceImpl<OrderMapper,OrderPO> implements OrderService  {

    @Value("${snowflake.workerId}")
    private long workerId;
    @Value("${snowflake.datacenterId}")
    private long datacenterId;


    private final OrderMapper orderMapper;
    private final ThreadPoolTaskExecutor asyncPool;

    private final ApplicationContext applicationContext;

    private final ConcurrentHashMap<Long, OrderPO> orders;

    private final Sequence snowFlakeGenerator;



    /**
     * 初始化变量，初始化对应定时任务
     * @param orderMapper order数据库访问
     * @param asyncPool 异步线程池（就是@Async标识的异步方法运行的线程池）
     * @param schedulerPool 定时任务线程池(就是@Sceduler标识的方法运行的线程池)
     * @param persistenceCron 定时任务表达式（在application.properties里面指定）
     * @param applicationContext 用于发布事件
     */

    @Autowired
    public OrderServiceImp(OrderMapper orderMapper, @Qualifier("taskExecutor") ThreadPoolTaskExecutor asyncPool, @Qualifier("schedulerPool") ThreadPoolTaskScheduler schedulerPool, @Value("${order.persistence.cron}") String persistenceCron, ApplicationContext applicationContext) {
        this.orderMapper = orderMapper;
        this.asyncPool = asyncPool;
        orders = orderMapper.selectList(new QueryWrapper<OrderPO>().ne("status",OrderStatus.END.getStatusCode())).stream().collect(Collectors.toConcurrentMap(OrderPO::getOrderId, Function.identity(),(v1,v2)->v1,ConcurrentHashMap::new));
        this.applicationContext = applicationContext;
        snowFlakeGenerator = new Sequence(workerId,datacenterId);
        schedulerPool.schedule(()->{
            dataPersistence();
            //异步
            //ayncPool.execute(this::removeEndOrder);
            //不使用异步了，减少比如方法调用，任务入队等的性能损耗，毕竟下面这个方法也不会阻塞线程
            //直接调用
            removeEndOrder();
            log.info("更新时间{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")));
        }, new CronTrigger(persistenceCron));
    }


    /**
     * 持久化数据
     */
    @PreDestroy
    private void dataPersistence(){
        updateBatchById(orders.values());
    }

    private void removeEndOrder(){
        orders.keySet()
                .stream()
                //因为没有提供这个缓存的remove api所以只有这一个方法会remove所以不会在get的时候存在被这个key被remove的情况
                .filter(l->OrderStatus.END.getStatusCode().equals(orders.get(l).getStatus()))
                .forEach(orders::remove);
    }



    @Override
    public OrderPO transToOrderPo(OrderVO orderVO) {
        return OrderTransfer.transToOrderPo(orderVO);
    }

    @Override
    public OrderVO transToOrderVo(OrderPO orderPO) {
        return OrderTransfer.transToOrderVo(orderPO);
    }

    /**
     * 若缓存中存在用缓存，否则走数据库
     * @param orderId 订单表编号
     * @return 订单
     */
    @Override
    public Optional<OrderVO> getOrder(Long orderId) {
        return Optional.ofNullable(orders.get(orderId))
                .or(() -> Optional.ofNullable(orderMapper.selectById(orderId)))
                .map(this::transToOrderVo);
    }

    /**
     * 若发送异常，则回滚，也不会加入缓存
     * @param orderVO 订单
     * @param userId 用户id
     * @exception MessageServiceException 发送信息产生的IOException会转化为这个异常
     * @return 订单编号
     */
    @Override
    @Transactional(rollbackFor = MessageServiceException.class)
    public Long addNewOrder(OrderVO orderVO, Integer userId) {
        OrderPO orderPO = transToOrderPo(orderVO);
        long orderId = snowFlakeGenerator.nextId();
        //手动赋值，不想走BeanCopier,性能问题,手动赋值调用的函数最后会被内联，jit不用担心实际性能问题
        //region 两个实体类属性赋值
        orderPO.setStatus(OrderStatus.ORDER.getStatusCode());
        orderVO.setStatus(OrderStatus.ORDER.getStatusDesc());
        orderPO.setUserId(userId);
        orderPO.setOrderId(orderId);
        orderVO.setOrderId(orderId);
        Date nowDate = DateUtil.localDateTimeToDate(LocalDateTime.now());
        orderPO.setStartTime(nowDate);
        orderVO.setStartTime(nowDate);
        //endregion

        //插入数据库，发信息给站点,加缓存
        //如果在发送异常的情况下，数据库回滚，缓存此时还未添加
        orderMapper.insert(orderPO);
        applicationContext.publishEvent(new NewOrderEvent(this,orderPO));
        orders.put(orderPO.getOrderId(), orderPO);

        return orderId;
    }

    /**
     *
     * @param userId 用户id
     * @param current 当前页数
     * @param size 每页多少
     * @return 订单集合
     */
    @Override
    public List<OrderVO> getHistoryOrder(Integer userId, int current, int size) {
        return orderMapper.selectPage(new Page<>(current, size), new LambdaQueryWrapper<OrderPO>().eq(OrderPO::getUserId, userId).eq(OrderPO::getStatus,OrderStatus.END.getStatusCode()))
                .getRecords().stream().map(this::transToOrderVo).collect(Collectors.toList());
    }

    /**
     *
     * @param orderId 订单编号
     * @param orderStatus 订单状态
     * @return 是否更新成功
     */
    @Override
    public boolean updateStatus(Long orderId, OrderStatus orderStatus) {
        OrderPO orderFromCache = orders.get(orderId);
        if (orderFromCache == null) {
            return orderMapper.updateStatusByOrderId(orderId, orderStatus.getStatusCode()) == 1;
        }
        orderFromCache.setStatus(orderStatus.getStatusCode());

        return true;
    }


    /**
     * 未结束的订单结束，所以只操作内存
     * @param orderId 订单编号
     * @return 是否成功
     */
    @Override
    public boolean endOrder(Long orderId) {
        OrderPO order = orders.get(orderId);
        if (order == null || ! OrderStatus.DELIVERING.getStatusCode().equals(order.getStatus())) {
            return false;
        }
        order.setEndTime(DateUtil.localDateTimeToDate(LocalDateTime.now()));
        order.setStatus(OrderStatus.END.getStatusCode());
        applicationContext.publishEvent(new EndOrderEvent(this,order));
        return true;
    }

    @Override
    public boolean deliveryOrder(Long orderId, String deviceId){
        OrderPO order = orders.get(orderId);
        if (order == null || ! OrderStatus.WAITING.getStatusCode().equals(order.getStatus()) ){
            return false;
        }
        order.setStatus(OrderStatus.DELIVERING.getStatusCode());
        order.setDeliveryStartTime(DateUtil.localDateTimeToDate(LocalDateTime.now()));
        order.setDeliveryDeviceId(deviceId);
        applicationContext.publishEvent(new DeliveryOrderEvent(this, order));
        return true;
    }
    @Override
    public boolean receiveOrder(Long orderId){
        OrderPO order = orders.get(orderId);
        if (order == null || ! OrderStatus.ORDER.getStatusCode().equals(order.getStatus()) ){
            return false;
        }
        order.setStatus(OrderStatus.WAITING.getStatusCode());
        return true;
    }

    @Override
    public List<OrderVO> getNowUndoOrders() {
        return orders.values().stream().map(this::transToOrderVo)
                .collect(Collectors.toList());
    }

    @Override
    public List<OrderVO> getNowUndoOrders(Integer stationId){
        return orders.values().stream()
                .filter(orderPO -> orderPO.getStationId().equals(stationId))
                .map(this::transToOrderVo)
                .collect(Collectors.toList());
    }

    @Override
    public List<OrderVO> getUserUndoOrder(Integer userId){
        return orderMapper.selectUserOrderIdByUserIdAndNotStatus(userId, OrderStatus.END.getStatusCode())
                .stream()
                .map(orders::get)
                .filter(o -> !o.getStatus().equals(OrderStatus.END.getStatusCode()))
                .map(this::transToOrderVo)
                .collect(Collectors.toList());
    }




}